前幾天的主題把自己認為寫 JS 一定要懂的前置觀念介紹了一下。那接下來的文章會盡量避免太過偏向討論特性的本質,而是會偏向實際應用的介紹,讓以後開發遇到問題時,能夠像工具書一樣查詢,畢竟這才符合我們第一天寫下的大綱與方向
函數的寫法有這幾種
function sayHi(){
console.log("Hi");
}
sayHi(); // Hi
var sayHi = function(){
console.log("Hi");
}
sayHi(); // Hi
這種寫法沒辦法透過 hoisting 來使用它,如果有看Day4的讀者應該就知道為什麼了。Ans: 第一行 Hoisting 的變數 sayHi 此時的值是 undefined
(function(a, b){
console.log(a + b);
})(10, 5); // 15
自調用函數不需要呼叫就可以自己立刻執行,方便建構自己的生存域。
(param1, param2, …, paramN) => { statements }
。singleParam => { statements }
,只有一個參數時,括號才能不加。(param1, param2, …, paramN) => expression
,expression 等於 { return expression; },若函數內只有 return 值而已,則可省略大括號和 return。所以這個
var sum = function(x, y){
return x + y;
};
console.log(sum(1, 2));
可以改寫成
var sum = (x, y) => x + y;
console.log(sum(1, 2));
是不是很簡短呢~
函數可以接受參數值傳入,但是沒有參數值傳入時,也可以事先設定預設值喔。
function guessWhat(str = 'Hi'){
console.log(str);
}
guessWhat(); // Hi
再來這邊要講一下,當傳入的參數大於函數事先定義的參數數量時,這些多出來的參數不會不見,還是會存在一個叫 arguments
的變數裡面(有定義的參數也會在裡面)。
function addNum(a, b){
var arg = arguments;
console.log(arg);
}
addNum(10, 20, 30); // [10, 20, 30]
大家都知道函數會有回傳值,也就是會有 return 值。
那這個特性也可以用來直接中斷函數,也就是直接 return 空值。
function sayHi(myStr = 'Hi'){
if(myStr == 'bye'){
return
}
console.log(myStr);
}
sayHi(); // Hi
sayHi('bye'); // show nothing
來人,還不先引用維基百科的介紹!
閉包(Closure)
是參照了自由變數
的函式。這個被參照的自由變數
將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的參照環境組合而成的實體。
簡單來說就是:閉包(Closure)
是擁有自由變數
的函數,只有當函數的自由變數
與當時環境綁定,此函數才稱為閉包,也就是建立函數不等於建立閉包。
PS. 自由變數
: 對於函數而言,既不是區域變數也不是參數的變數。
聽的霧煞煞嗎?
沒關係,我們直接來看例子,請問Q1
會印出什麼?
function outer(){
var myStr = "Hi";
function sayHi(){
console.log(myStr);
}
return sayHi;
}
var myFun = outer();
myFun(); // Q1=?
Ans:Q1
= Hi
咦,可是 myFun
是 outer
return 出來的 function,也就是 sayHi
。但是 sayHi
中並沒有 myStr
的變數阿,能夠使用的話,也應該是在 outer
中去呼叫 sayHi
,然後由於 Scope 的特性,所以能夠使用 myStr
。
沒有道理 return 出來還能夠使用 outer
函數中的變數吧,這太扯了...?
別急阿,讓我們想想上面提到閉包的定義,所以可以理解成,
myStr
在函數執行完就消失,sayHi
參考到變數 myStr
,sayHi
被 return 給全域變數 myFun
時,myStr
活下來了。而此時的變數 myStr
,對於 sayHi
來說就屬於自由變數,而 sayHi
也就建立了閉包,它把 myStr
關進了自己的世界。
瞧瞧這可怕的佔有欲(X
還是不太清楚嗎,沒關係我們可以再多看幾個例子~
function outer() {
var a = 10;
function addNum(b) {
return a + b;
}
return addNum;
}
var myFun = outer();
console.log(myFun(10)); // Q2=?
console.log(myFun(20)); // Q3=?
沒錯,答案是
Ans:Q2
= 20Q3
= 30
Q: (舉手)請問這樣我可以說自己是閉包達人或是精通閉包了嗎?
A: Hmm...應該沒有人會這樣稱呼自己,讓我們再多看幾個範例好了...
上方的例子中,變數 a
被關入閉包,突破作用域活了下來,但是有一個觀念要牢記:
閉包關閉的是變數,而不是變數所參考的值。
所以把程式碼改成這樣,請問 Q4
是 666 還是 11 呢?
function outer() {
var a = 10;
function addNum(b) {
return a + b;
}
a = 665;
return addNum;
}
var myFun = outer();
console.log(myFun(1)); // Q4=?
沒錯,答案是
Ans:Q4
= 666
因為我們已經有先做了小抄,了解到「閉包關閉的是變數,而不是變數所參考的值」,
所以在原環境中,return addNum
之前改變了變數 a
的值,也更新了關入閉包中的變數的值。
那接下來還有另外一個重點,
閉包關入的變數是獨立的,也就是每次呼叫建立閉包時,都是根據當時的環境來產生。
有了這個觀念我們來看看這個,請問 Q5
會印出 31 還是 32 ?
也就是 myFun1
執行之後,閉包中的變數 a
是 10 還是 11 呢?
function outer() {
var a = 10;
function addNum(b) {
a = a + 1;
return a + b;
}
return addNum;
}
var myFun1 = outer();
var myFun2 = outer();
console.log(myFun1(10)); // 21
console.log(myFun2(20)); // Q5=?
沒錯,答案是,
Ans:Q5
= 31
因為 myFun1
和 myFun2
中的閉包是各自獨立的,myFun1
執行之後,變數 a
變成 11,但是不關 myFun2
的事情。
小提醒,myFun1
、myFun2
執行後,各自關入的變數還是保存著,
...
console.log(myFun1(10)); // 21
console.log(myFun2(20)); // Q5=31
console.log(myFun1(10)); // Q6=?
所以 Q6
理所當然的也就是 22 啦。
謝謝樓主分享,我了解了!
Hmm...越想越不對,我知道這個幹嘛? 它能實際應用在哪裡?
別緊張還沒分享完嘛Q_Q,讓我們明天再來講講它的實際應用!
那今日的分享到這,我們明天見